/**
 * DispatcherSystem.java
 * ************************** 
 * @date Apr 19, 2011
 * @author Michele Tamburini
 * 
 * **************************
 */
package systems.dispatcher;

import infrastructureAPI.APIResponse;
import infrastructureAPI.Requester;
import infrastructureAPI.Service;
import infrastructureAPI.ServiceHandler;
import infrastructureAPI.APIMessages.GeneralResponseMessage;
import infrastructureAPI.APIMessages.RequestMessage;
import infrastructureAPI.APIMessages.ResponseMessage;
import infrastructureAPI.algorithms.APIFunction;

import java.net.InetAddress;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Vector;

import networkRmi.NetworkManager;
import systems.network.NetworkSystemsAPI;

/** 
 * Description: 
 * The DispatcherSystem is a system of the cloud able to 
 * collect the RequestMessages and execute the APIFunction
 * contained. 
 * It starts a new Thread for each APIFunction.
 * It's a {@link ServiceHandler} and maintains a List of 
 * the registered {@link Service}(s).
 */
public class DispatcherSystem extends Thread
	implements ServiceHandler, systems.System{
	
	/**
	 * If true shows all the output from the DispacherSystem,
	 * such as the arrival of a message, the submission of a
	 * request or the warnings and errors.
	 */
	protected static boolean SHOW_DISPATCHER_SYSTEM_OUTPUTS = true;
	
	/**
	 * Name of the thread running for the current dispatcher.
	 */
	protected static String DISPATCHER_THREAD = "DispatcherThread";
	
	protected static String DISPATCHER_STRING = "DISPATCHER SYSTEM";
	protected static String DISPATCHER_ERROR_STRING = "DISPATCHER SYSTEM ERROR";
	
	/**
	 * The NetworkSystemsAPI used by this dispatcher.
	 */
	protected NetworkSystemsAPI netMan;
	
	protected boolean running = true;
	protected boolean hasToStop = false;
	
	/**
	 * List containing all the registered service in this 
	 * @uml.associationEnd  multiplicity="(0 -1)" ordering="true" container="java.util.List" aggregation="shared" inverse="infrastructureAPI.Service"
	 */
	
	protected List<Service> registeredServices;
	
	public DispatcherSystem(NetworkManager netMan, 
			List<Service> runningServices){
		this.netMan = new DispatcherNetworkManager(netMan);
		this.setName(DISPATCHER_THREAD);
		registeredServices = new Vector<Service>();
		registeredServices.addAll(runningServices);
		registeredServices.add(this.netMan);
		
	}
	
	public void run(){
		RequestMessage msg;
		Vector<Service> selectedServices;
		List<Class<?>> requiredServices;
		Thread apiFunctionThread;
		boolean goodServices;
		APIResponse responseBody = null;
		while(running){
			msg = (RequestMessage) netMan.receive();
			
			if(msg != null){

				// --------------------------------------------------------
				// Extracting and elaborating the collected RequestMessage  
				// --------------------------------------------------------
				if(SHOW_DISPATCHER_SYSTEM_OUTPUTS)
					System.out.println(DISPATCHER_STRING+"\n"+
							currentThread().getName()+" "+
							"received message: "+msg);
				APIFunction request = msg.getFunction();
				// collecting all required services:
				// --------------------------------------------------------
				requiredServices = request.requiredServices();
				selectedServices = retrieveServices(requiredServices);
				goodServices = verifyRetrievedServices(requiredServices, selectedServices);

				// build the body of the response:
				// --------------------------------------------------------
				if(!goodServices){
					List<Class<?>> missingServices = extractMissingServices(requiredServices,
							selectedServices);
					responseBody = new NotFoundServices(request, missingServices);
					if(SHOW_DISPATCHER_SYSTEM_OUTPUTS)
						System.out.println(DISPATCHER_ERROR_STRING+": " +
								"Missing Services: "+missingServices+"\n" +
								"for request: "+request);
				}else{
					responseBody = new AffirmativeDispatcherResponse();
				}

				// --------------------------------------------------------
				// Send ResponseMessage
				// --------------------------------------------------------
				if(request.requireReply()){
					sendResponseMessage(msg, responseBody);
				}
				
				// --------------------------------------------------------
				// Running the APIFunction contained into the message
				// --------------------------------------------------------
				if(goodServices){
					
					// --------------------------------------------------------
					// Initialization of the required Service(s)
					// -------------------------------------------------------- 
					boolean successInitialization = request.initializeServices(selectedServices);
					
					if(SHOW_DISPATCHER_SYSTEM_OUTPUTS){
						if(successInitialization){
							System.out.println(DISPATCHER_STRING+": " +
									"request \""+ request.getClass().getSimpleName()
									+"\" correctly started.");
						}else{
							System.out.println(DISPATCHER_ERROR_STRING+": " +
									"couldn't start request "+request);
						}
					}
					
					// --------------------------------------------------------
					// Start a new thread
					// --------------------------------------------------------
					/* WARNING: the DispatcherSystem must ensure to execute 
					 * of each APIFunction its own run() function
					 * before the registration or de-registration methods. 
					*/
					apiFunctionThread = new Thread(request);
					try {
						apiFunctionThread.start();
						apiFunctionThread.join();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					if(apiFunctionThread.isInterrupted()){
						responseBody = new RequestAborted("Cannot accomplish request: "+
								request);
						sendResponseMessage(msg, responseBody);
					}
					

					// --------------------------------------------------------
					// Adding the APIFunction to the registered Service(s) if
					// it provides a new feature to the cloud
					// --------------------------------------------------------
					if(request.requireSystemRegistration()){
						Service service = request.extractSuppliedService();
						registerService(service);
						if(SHOW_DISPATCHER_SYSTEM_OUTPUTS){
							System.out.println(DISPATCHER_STRING+": Registered additional service: "+
									service.getServiceClass().getSimpleName());
						}
					}

					// Verify if the request needs the DeRegistration of a Service
					// from this node
					// --------------------------------------------------------
					if(request.requireSystemDeRegistration()){
						Service service = request.extractServiceToDeRegister();
						deRegisterService(service);
						if(SHOW_DISPATCHER_SYSTEM_OUTPUTS){
							System.out.println(DISPATCHER_STRING+": DeRegistered the service: "+
									service.getServiceClass().getSimpleName());
						}
					}
				}
				msg = null;
			}
		}
	}
	

	/**
	 * @param requester
	 * @return
	 */
	private boolean sendResponseMessage(RequestMessage msg, APIResponse responseBody) {
		Requester requester = msg.replyTo();
		ResponseMessage replyMessage = new GeneralResponseMessage(msg.getDest(),
				requester, responseBody);
		if(SHOW_DISPATCHER_SYSTEM_OUTPUTS)
			System.out.println("replying to: "+requester);

		try {
			InetAddress address =  requester.getNodeAddress();
			Integer port = requester.getPort();
			String service = requester.getRegisteredService();

			netMan.contactRemoteService(address, 
					port , service, replyMessage);
		} catch (RemoteException e) {
			e.printStackTrace();
			if(SHOW_DISPATCHER_SYSTEM_OUTPUTS)
				System.out.println(DISPATCHER_ERROR_STRING+": problem connecting to: "+requester);
		} catch (NotBoundException e) {
			e.printStackTrace();
			if(SHOW_DISPATCHER_SYSTEM_OUTPUTS)
				System.out.println(DISPATCHER_ERROR_STRING+": register not found on: "+requester+"" +
				"\nIs the request launcher listening for incoming responses?");
		}
		return true;
	}

	/**
	 * @param requiredServices
	 * @param selectedServices
	 * @return
	 */
	private List<Class<?>> extractMissingServices(
			List<Class<?>> requiredServices, Vector<Service> selectedServices) {
		for(Service service : selectedServices){
			requiredServices.remove(service.getClass());
		}
		return requiredServices;
	}

	/**
	 * @param requiredServices
	 * @param selectedServices
	 * @return
	 */
	private boolean verifyRetrievedServices(List<Class<?>> requiredServices,
			Vector<Service> selectedServices) {
		boolean ret = false;
		ret = requiredServices.size() == selectedServices.size();
		return ret;
	}

	/**
	 * @param requiredServices
	 * @return
	 */
	private Vector<Service> retrieveServices(List<Class<?>> requiredServices) {
		Vector<Service> retVec = new Vector<Service>();
		boolean found;
		Service serv;
		Class<?> clas;
		System.out.println("Dispatcher: retrieveServices");
		// TODO
		// WARNING: O(N^2) 
		// use something different... maybe a java.util.Map ???
		for(int i =0; i<registeredServices.size(); i++){
			found = false;
			for(int j=0; j < requiredServices.size() && !found; j++){
				serv = registeredServices.get(i);
				clas = requiredServices.get(j);
//				System.out.println("service: "+serv+" class: "+clas);
				if( serv.getServiceClass().equals(clas) ){
					retVec.add(serv);
					found = true;
				}
			}
		}
		return retVec;
	}

	public void stopDispatcherSystem(){
		hasToStop = true;
		this.interrupt();
	}

	@Override
	public boolean deRegisterService(Service s) {
		return registeredServices.remove(s);
	}

	@Override
	public List<Service> getRegisteredServices() {
		return registeredServices;
	}

	@Override
	public boolean registerService(Service s) {
		return registeredServices.add(s);
	}

}
